/**
 * Copyright (C) 2010 David Simonek <dafesimonek@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.dafe.partyband.dochandler;

import com.sun.star.bridge.UnoUrlResolver;
import com.sun.star.bridge.XUnoUrlResolver;
import com.sun.star.comp.helper.BootstrapException;
import com.sun.star.comp.helper.ComponentContext;
import com.sun.star.comp.helper.ComponentContextEntry;
import com.sun.star.comp.loader.JavaLoader;
import com.sun.star.container.XSet;
import com.sun.star.lang.XInitialization;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.lang.XSingleComponentFactory;
import com.sun.star.lib.util.NativeLibraryLoader;
import com.sun.star.loader.XImplementationLoader;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Hashtable;
import java.util.Random;
import org.openide.util.Exceptions;

public final class OOBootstrap {

    private static void insertBasicFactories(
            XSet xSet, XImplementationLoader xImpLoader)
            throws Exception {
        // insert the factory of the loader
        xSet.insert(xImpLoader.activate(
                "com.sun.star.comp.loader.JavaLoader", null, null, null));

        // insert the factory of the URLResolver
        xSet.insert(xImpLoader.activate(
                "com.sun.star.comp.urlresolver.UrlResolver", null, null, null));

        // insert the bridgefactory
        xSet.insert(xImpLoader.activate(
                "com.sun.star.comp.bridgefactory.BridgeFactory", null, null, null));

        // insert the connector
        xSet.insert(xImpLoader.activate(
                "com.sun.star.comp.connections.Connector", null, null, null));

        // insert the acceptor
        xSet.insert(xImpLoader.activate(
                "com.sun.star.comp.connections.Acceptor", null, null, null));
    }

    /** Bootstraps an initial component context with service manager and basic
    jurt components inserted.
    @param context_entries the hash table contains mappings of entry names (type string) to
    context entries (type class ComponentContextEntry).
    @return a new context.
     */
    private static XComponentContext createInitialComponentContext(Hashtable context_entries)
            throws Exception {
        XImplementationLoader xImpLoader = (XImplementationLoader) UnoRuntime.queryInterface(
                XImplementationLoader.class, new JavaLoader());

        // Get the factory of the ServiceManager
        XSingleComponentFactory smgr_fac = (XSingleComponentFactory) UnoRuntime.queryInterface(
                XSingleComponentFactory.class, xImpLoader.activate(
                "com.sun.star.comp.servicemanager.ServiceManager", null, null, null));

        // Create an instance of the ServiceManager
        XMultiComponentFactory xSMgr = (XMultiComponentFactory) UnoRuntime.queryInterface(
                XMultiComponentFactory.class, smgr_fac.createInstanceWithContext(null));

        // post init loader
        XInitialization xInit = (XInitialization) UnoRuntime.queryInterface(
                XInitialization.class, xImpLoader);
        Object[] args = new Object[]{xSMgr};
        xInit.initialize(args);

        // initial component context
        if (context_entries == null) {
            context_entries = new Hashtable(1);
        }
        // add smgr
        context_entries.put(
                "/singletons/com.sun.star.lang.theServiceManager",
                new ComponentContextEntry(null, xSMgr));
        // ... xxx todo: add standard entries
        XComponentContext xContext = new ComponentContext(context_entries, null);

        // post init smgr
        xInit = (XInitialization) UnoRuntime.queryInterface(
                XInitialization.class, xSMgr);
        args = new Object[]{null, xContext}; // no registry, default context
        xInit.initialize(args);

        XSet xSet = (XSet) UnoRuntime.queryInterface(XSet.class, xSMgr);
        // insert the service manager
        xSet.insert(smgr_fac);
        // and basic jurt factories
        insertBasicFactories(xSet, xImpLoader);

        return xContext;
    }

    /**
     * Bootstraps the component context from a UNO installation.
     *
     * @return a bootstrapped component context.
     *
     * @since UDK 3.1.0
     */
    private static XComponentContext bootstrap(ClassLoader loader)
            throws BootstrapException {
        XComponentContext xContext = null;
        try {
            // create default local component context
            XComponentContext xLocalContext =
                    createInitialComponentContext(null);
            if (xLocalContext == null) {
                throw new BootstrapException("no local component context!");
            }
            // find office executable relative to this class's class loader
            String sOffice =
                    System.getProperty("os.name").startsWith("Windows")
                    ? "soffice.exe" : "soffice";
            File fOffice = NativeLibraryLoader.getResource(loader, sOffice);
            if (fOffice == null) {
                throw new BootstrapException("no office executable found!");
            }
            // create random pipe name
            String sPipeName = "uno"
                    + Long.toString((new Random()).nextLong() & 0x7fffffffffffffffL);
            // create call with arguments
            String[] cmdArray = new String[7];
            cmdArray[0] = fOffice.getPath();
            cmdArray[1] = "-nologo";
            cmdArray[2] = "-nodefault";
            cmdArray[3] = "-norestore";
            cmdArray[4] = "-nocrashreport";
            cmdArray[5] = "-nolockcheck";
            //cmdArray[6] = "-accept=pipe,name=" + sPipeName + ";urp;";
            cmdArray[6] = "-accept=socket,host=localhost,port=8100;urp;";

            // start office process
            Process p = Runtime.getRuntime().exec(cmdArray);
            pipe(p.getInputStream(), System.out, "CO> ");
            pipe(p.getErrorStream(), System.err, "CE> ");

            // initial service manager
            XMultiComponentFactory xLocalServiceManager =
                    xLocalContext.getServiceManager();
            if (xLocalServiceManager == null) {
                throw new BootstrapException("no initial service manager!");
            }
            // create a URL resolver
            XUnoUrlResolver xUrlResolver =
                    UnoUrlResolver.create(xLocalContext);

            // connection string
             /*String sConnect = "uno:pipe,name=" + sPipeName +
            ";urp;StarOffice.ComponentContext";*/

            // connection string
            String sConnect = "uno:socket,host=localhost,port=8100;urp;StarOffice.ComponentContext";

            // wait until office is started
            for (int i = 0;; ++i) {
                try {
                    // try to connect to office
                    Object context = xUrlResolver.resolve(sConnect);
                    xContext = (XComponentContext) UnoRuntime.queryInterface(
                            XComponentContext.class, context);
                    if (xContext == null) {
                        throw new BootstrapException("no component context!");
                    }
                    break;
                } catch (com.sun.star.connection.NoConnectException ex) {
                    // Wait 500 ms, then try to connect again, but do not wait
                    // longer than 5 min (= 600 * 500 ms) total:
                    if (i == 600) {
                        throw new BootstrapException(ex.toString());
                    }
                    Thread.sleep(500);
                }
            }
        } catch (BootstrapException e) {
            throw e;
        } catch (java.lang.RuntimeException e) {
            throw e;
        } catch (java.lang.Exception e) {
            throw new BootstrapException(e);
        }

        return xContext;
    }

    private static void pipe(
            final InputStream in, final PrintStream out, final String prefix) {

        new Thread("Pipe: " + prefix) {

            @Override
            public void run() {
                BufferedReader r = new BufferedReader(
                        new InputStreamReader(in));
                try {
                    for (;;) {
                        String s = r.readLine();
                        if (s == null) {
                            break;
                        }
                        out.println(prefix + s);
                    }
                } catch (java.io.IOException e) {
                    e.printStackTrace(System.err);
                }
            }
        }.start();
    }

    public static XComponentContext bootstrap() throws BootstrapException {
        URL[] jarList = null;
        try {
            jarList = new URL[]{new File(getOfficeRootPath(System.getProperty("os.name"))).toURI().toURL()};
        } catch (MalformedURLException ex) {
            Exceptions.printStackTrace(ex);
        }
        URLClassLoader loader = new URLClassLoader(jarList);
        return bootstrap(loader);
    }

    private static final String [] WIN_DIRS = new String[] {
        "C:\\Program Files\\OpenOffice.org 3.0\\program",
        "C:\\Program Files\\OpenOffice.org 2.0\\program",
    };
    private static final String WIN_ROOT = "C:\\Program Files";
    private static final String LINUX_DIR = "/usr/lib/openoffice/program";
    private static final String LINUX_ROOT = "/usr/lib";
    private static final String DIR_NAME_PREFIX = "openoffice";
    private static final String BIN_DIR_NAME = "program";

    static String getOfficeRootPath (String osName) {
        osName = osName.toLowerCase();
        if (osName.startsWith("windows")) {
            // try common locations
            for (int i = 0; i < WIN_DIRS.length; i++) {
                String curDir = WIN_DIRS[i];
                if (new File(curDir).exists()) {
                    return curDir;
                }
            }
            // try to find suitable install directory
            String foundDir = findDirectory(WIN_ROOT);
            if (foundDir != null) {
                return foundDir;
            }
        } else if (osName.startsWith("linux") || osName.startsWith("unix")) {
            if (new File(LINUX_DIR).exists()) {
                return LINUX_DIR;
            }
            // try to find suitable install directory
            String foundDir = findDirectory(LINUX_ROOT);
            if (foundDir != null) {
                return foundDir;
            }
        }
        // TBD - what to do?
        return System.getProperty("user.home");
    }

    private static String findDirectory (String rootDir) {
        File rootDirFile = new File(rootDir);
        if (!rootDirFile.exists() || !rootDirFile.isDirectory()) {
            return null;
        }
        String[] children = rootDirFile.list();
        for (int i = 0; i < children.length; i++) {
            if (children[i].toLowerCase().startsWith(DIR_NAME_PREFIX)) {
                File possibleDir = new File(rootDirFile, children[i]);
                if (possibleDir.isDirectory()) {
                    File binDir = new File(possibleDir, BIN_DIR_NAME);
                    if (binDir.exists() && binDir.isDirectory()) {
                        return binDir.getPath();
                    }
                }
            }
        }
        return null;
    }

}
